package fr.sharpe.app.earthvisualizationproject.ui;

import fr.sharpe.app.earthvisualizationdatamodel.Data;
import fr.sharpe.app.earthvisualizationproject.model.SelectedObjectData;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.layers.AbstractLayer;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.render.OrderedRenderable;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.OGLTextRenderer;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;

import javax.media.opengl.GL;

import com.sun.opengl.util.j2d.TextRenderer;

public class DataDisplayLayer extends AbstractLayer
{
	// Units constants
	public final static String UNIT_METRIC = "gov.nasa.worldwind.ScalebarLayer.Metric";
	public final static String UNIT_IMPERIAL = "gov.nasa.worldwind.ScalebarLayer.Imperial";

	public static long index = 0;

	private SelectedObjectData selectedObject = SelectedObjectData.getInstance();
	private Data tempData = null;

	// Display parameters - TODO: make configurable
	private Dimension size = new Dimension(150, 10);
	private Color color = Color.white;
	private int borderWidth = 20;
	private String position = AVKey.SOUTHWEST;
	private String resizeBehavior = AVKey.RESIZE_SHRINK_ONLY;
	private String unit = UNIT_METRIC;
	private Font defaultFont = Font.decode("Arial-PLAIN-12");
	private double toViewportScale = 0.2;

	private PickSupport pickSupport = new PickSupport();
	private Vec4 locationCenter = null;
	private Vec4 locationOffset = null;
	private double pixelSize;

	// Draw it as ordered with an eye distance of 0 so that it shows up in front of most other things.
	// TODO: Add general support for this common pattern.
	private OrderedIcon orderedImage = new OrderedIcon();

	private class OrderedIcon implements OrderedRenderable
	{
		public double getDistanceFromEye()
		{
			return 0;
		}

		public void pick(DrawContext dc, Point pickPoint)
		{
			DataDisplayLayer.this.draw(dc);
		}

		public void render(DrawContext dc)
		{
			DataDisplayLayer.this.draw(dc);
		}
	}

	/**
	 * Renders a scalebar graphic in a screen corner
	 */
	 public DataDisplayLayer()
	 {
		 setPickEnabled(false);
	 }

	 // Public properties

	 /**
	  * Get the apparent pixel size in meter at the reference position.
	  * @return the apparent pixel size in meter at the reference position.
	  */
	 public double getPixelSize()
	 {
		 return this.pixelSize;
	 }

	 /**
	  * Get the scalebar graphic Dimension (in pixels)
	  * @return the scalebar graphic Dimension
	  */
	 public Dimension getSize()
	 {
		 return this.size;
	 }

	 /**
	  * Set the scalebar graphic Dimenion (in pixels)
	  * @param size the scalebar graphic Dimension
	  */
	 public void setSize(Dimension size)
	 {
		 if (size == null)
		 {
			 String message = Logging.getMessage("nullValue.DimensionIsNull");
			 Logging.logger().severe(message);
			 throw new IllegalArgumentException(message);
		 }
		 this.size = size;
	 }

	 /**
	  * Get the scalebar color
	  * @return  the scalebar Color
	  */
	 public Color getColor()
	 {
		 return this.color;
	 }

	 /**
	  * Set the scalbar Color
	  * @param color the scalebar Color
	  */
	 public void setColor(Color color)
	 {
		 if (color == null)
		 {
			 String msg = Logging.getMessage("nullValue.ColorIsNull");
			 Logging.logger().severe(msg);
			 throw new IllegalArgumentException(msg);
		 }
		 this.color = color;
	 }

	 /** Returns the scalebar-to-viewport scale factor.
	  *
	  * @return the scalebar-to-viewport scale factor
	  */
	 public double getToViewportScale()
	 {
		 return toViewportScale;
	 }

	 /**
	  * Sets the scale factor applied to the viewport size to determine the displayed size of the scalebar. This
	  * scale factor is used only when the layer's resize behavior is AVKey.RESIZE_STRETCH or AVKey.RESIZE_SHRINK_ONLY.
	  * The scalebar's width is adjusted to occupy the proportion of the viewport's width indicated by
	  * this factor. The scalebar's height is adjusted to maintain the scalebar's Dimension aspect ratio.
	  *
	  * @param toViewportScale the scalebar to viewport scale factor
	  */
	 public void setToViewportScale(double toViewportScale)
	 {
		 this.toViewportScale = toViewportScale;
	 }

	 public String getPosition()
	 {
		 return this.position;
	 }

	 /**
	  * Sets the relative viewport location to display the scalebar. Can be one of AVKey.NORTHEAST,
	  * AVKey.NORTHWEST, AVKey.SOUTHEAST (the default), or AVKey.SOUTHWEST. These indicate the corner of the viewport.
	  *
	  * @param position the desired scalebar position
	  */
	 public void setPosition(String position)
	 {
		 if (position == null)
		 {
			 String msg = Logging.getMessage("nullValue.PositionIsNull");
			 Logging.logger().severe(msg);
			 throw new IllegalArgumentException(msg);
		 }
		 this.position = position;
	 }

	 /**
	  * Returns the current scalebar center location.
	  *
	  * @return the current location center. May be null.
	  */
	 public Vec4 getLocationCenter()
	 {
		 return locationCenter;
	 }

	 /**
	  * Specifies the screen location of the scalebar center. May be null. If this value is
	  * non-null, it overrides the position specified by #setPosition. The location is specified in pixels. The origin is
	  * the window's lower left corner. Positive X values are to the right of the origin, positive Y values are upwards
	  * from the origin. The final scalebar location will be affected by the currently specified location offset if a
	  * non-null location offset has been specified (see #setLocationOffset).
	  *
	  * @param locationCenter the scalebar center. May be null.
	  * @see #setPosition, #setLocationOffset
	  */
	 public void setLocationCenter(Vec4 locationCenter)
	 {
		 this.locationCenter = locationCenter;
	 }

	 /**
	  * Returns the current location offset. See #setLocationOffset for a description of the offset and its values.
	  *
	  * @return the location offset. Will be null if no offset has been specified.
	  */
	 public Vec4 getLocationOffset()
	 {
		 return locationOffset;
	 }

	 /**
	  * Specifies a placement offset from the scalebar's position on the screen.
	  *
	  * @param locationOffset the number of pixels to shift the scalebar from its specified screen position. A
	  *                       positive X value shifts the image to the right. A positive Y value shifts the image up. If
	  *                       null, no offset is applied. The default offset is null.
	  * @see #setLocationCenter, #setPosition
	  */
	 public void setLocationOffset(Vec4 locationOffset)
	 {
		 this.locationOffset = locationOffset;
	 }

	 /**
	  * Returns the layer's resize behavior.
	  *
	  * @return the layer's resize behavior
	  */
	 public String getResizeBehavior()
	 {
		 return resizeBehavior;
	 }

	 /**
	  * Sets the behavior the layer uses to size the scalebar when the viewport size changes, typically when the
	  * World Wind window is resized. If the value is AVKey.RESIZE_KEEP_FIXED_SIZE, the scalebar size is kept to the size
	  * specified in its Dimension scaled by the layer's current icon scale. If the value is AVKey.RESIZE_STRETCH,
	  * the scalebar is resized to have a constant size relative to the current viewport size. If the viewport shrinks the
	  * scalebar size decreases; if it expands then the scalebar enlarges. If the value is
	  * AVKey.RESIZE_SHRINK_ONLY (the default), scalebar sizing behaves as for AVKey.RESIZE_STRETCH but it will
	  * not grow larger than the size specified in its Dimension.
	  *
	  * @param resizeBehavior the desired resize behavior
	  */
	 public void setResizeBehavior(String resizeBehavior)
	 {
		 this.resizeBehavior = resizeBehavior;
	 }

	 public int getBorderWidth()
	 {
		 return borderWidth;
	 }

	 /**
	  * Sets the scalebar offset from the viewport border.
	  *
	  * @param borderWidth the number of pixels to offset the scalebar from the borders indicated by {@link
	  * #setPosition(String)}.
	  */
	 public void setBorderWidth(int borderWidth)
	 {
		 this.borderWidth = borderWidth;
	 }

	 public String getUnit()
	 {
		 return this.unit;
	 }

	 /**
	  * Sets the unit the scalebar uses to display distances.
	  * Can be one of {@link #UNIT_METRIC} (the default),
	  * or {@link #UNIT_IMPERIAL}.
	  *
	  * @param unit the desired unit
	  */
	 public void setUnit(String unit)
	 {
		 this.unit = unit;
	 }

	 /**
	  * Get the scalebar legend Fon
	  * @return the scalebar legend Font
	  */
	 public Font getFont()
	 {
		 return this.defaultFont;
	 }

	 /**
	  * Set the scalebar legend Fon
	  * @param font the scalebar legend Font
	  */
	 public void setFont(Font font)
	 {
		 if (font == null)
		 {
			 String msg = Logging.getMessage("nullValue.FontIsNull");
			 Logging.logger().severe(msg);
			 throw new IllegalArgumentException(msg);
		 }
		 this.defaultFont = font;
	 }

	 // Rendering
	 @Override
	 public void doRender(DrawContext dc)
	 {
		 dc.addOrderedRenderable(this.orderedImage);
	 }

	 @Override
	 public void doPick(DrawContext dc, Point pickPoint)
	 {
		 // Delegate drawing to the ordered renderable list
		 dc.addOrderedRenderable(this.orderedImage);
	 }

	 // Rendering
	 public void draw(DrawContext dc)
	 {
		 GL gl = dc.getGL();

		 boolean attribsPushed = false;
		 boolean modelviewPushed = false;
		 boolean projectionPushed = false;

		 try
		 {
			 gl.glPushAttrib(GL.GL_DEPTH_BUFFER_BIT
					 | GL.GL_COLOR_BUFFER_BIT
					 | GL.GL_ENABLE_BIT
					 | GL.GL_TEXTURE_BIT
					 | GL.GL_TRANSFORM_BIT
					 | GL.GL_VIEWPORT_BIT
					 | GL.GL_CURRENT_BIT);
			 attribsPushed = true;

			 gl.glDisable(GL.GL_TEXTURE_2D);		// no textures

			 gl.glEnable(GL.GL_BLEND);
			 gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
			 gl.glDisable(GL.GL_DEPTH_TEST);

			 double width = this.size.width;
			 double height = this.size.height;

			 // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight)
			 // into the GL projection matrix.
			 java.awt.Rectangle viewport = dc.getView().getViewport();
			 gl.glMatrixMode(javax.media.opengl.GL.GL_PROJECTION);
			 gl.glPushMatrix();
			 projectionPushed = true;
			 gl.glLoadIdentity();
			 double maxwh = width > height ? width : height;
			 gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh);

			 gl.glMatrixMode(GL.GL_MODELVIEW);
			 gl.glPushMatrix();
			 modelviewPushed = true;
			 gl.glLoadIdentity();

			 // Scale to a width x height space
			 // located at the proper position on screen
			 double scale = this.computeScale(viewport);
			 Vec4 locationSW = this.computeLocation(viewport, scale);
			 gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z());
			 gl.glScaled(scale, scale, 1);

			 // Compute scale size in real world
			 Position referencePosition = dc.getViewportCenterPosition();
			 if(referencePosition != null)
			 {
				 Vec4 groundTarget = dc.getGlobe().computePointFromPosition(referencePosition);
				 Double distance = dc.getView().getEyePoint().distanceTo3(groundTarget);
				 this.pixelSize = dc.getView().computePixelSizeAtDistance(distance);
				 Double scaleSize = this.pixelSize * width * scale;  // meter
				 String unitLabel = "m";
				 if(this.unit.equals(UNIT_METRIC))
				 {
					 if(scaleSize > 10000)
					 {
						 scaleSize /= 1000;
						 unitLabel = "Km";
					 }
				 }
				 else if(this.unit.equals(UNIT_IMPERIAL))
				 {
					 scaleSize *= 3.280839895; // feet
					 unitLabel = "ft";
					 if(scaleSize > 5280)
					 {
						 scaleSize /= 5280;
						 unitLabel = "mile(s)";
					 }
				 }

				 // Rounded division size
				 int pot =  (int)Math.floor(Math.log10(scaleSize));
				 if (!Double.isNaN(pot))
				 {
					 int digit = Integer.parseInt(String.format("%.0f", scaleSize).substring(0, 1));
					 double divSize = digit * Math.pow(10, pot);
					 if(digit >= 5)
						 divSize = 5 * Math.pow(10, pot);
					 else if (digit >= 2)
						 divSize = 2 * Math.pow(10, pot);
					 double divWidth = width * divSize / scaleSize;

					 // Draw scale
					 if (!dc.isPickingMode())
					 {
						 // Set color using current layer opacity
						 Color backColor = this.getBackgroundColor(this.color);

						 // Draw label
						 String label = "fgdsdfsd \n sdfkgsd sjgjfsdhgfkdghhdfkjgd"; //String.format("%.0f ", divSize) + unitLabel+
						 gl.glLoadIdentity();
						 gl.glDisable(GL.GL_CULL_FACE);
						 drawData(dc,locationSW.add3(new Vec4(divWidth * scale / 2 + (width - divWidth) / 2, height * scale, 0)));
					 }
					 else
					 {
						 // Picking
						 this.pickSupport.clearPickList();
						 this.pickSupport.beginPicking(dc);
						 // Draw unique color across the map
						 Color color = dc.getUniquePickColor();
						 int colorCode = color.getRGB();
						 // Add our object(s) to the pickable list
						 this.pickSupport.addPickableObject(colorCode, this, referencePosition, false);
						 gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
						 gl.glTranslated((width - divWidth) / 2, 0d, 0d);
						 this.drawRectangle(dc, divWidth, height);
						 // Done picking
						 this.pickSupport.endPicking(dc);
						 this.pickSupport.resolvePick(dc, dc.getPickPoint(), this);

					 }
				 }

			 }
		 }
		 finally
		 {
			 if (projectionPushed)
			 {
				 gl.glMatrixMode(GL.GL_PROJECTION);
				 gl.glPopMatrix();
			 }
			 if (modelviewPushed)
			 {
				 gl.glMatrixMode(GL.GL_MODELVIEW);
				 gl.glPopMatrix();
			 }
			 if (attribsPushed)
				 gl.glPopAttrib();
		 }
	 }

	 // Draw scale rectangle
	 private void drawRectangle(DrawContext dc, double width, double height)
	 {
		 GL gl = dc.getGL();
		 gl.glBegin(GL.GL_POLYGON);
		 gl.glVertex3d(0, height ,0);
		 gl.glVertex3d(0, 0 ,0);
		 gl.glVertex3d(width, 0 ,0);
		 gl.glVertex3d(width, height ,0);
		 gl.glVertex3d(0, height ,0);
		 gl.glEnd();
	 }

	 private void drawData(DrawContext dc, Vec4 screenPoint)
	 {
		 TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(),this.defaultFont);

		 int x = (int) screenPoint.x();
		 int y = (int) screenPoint.y();

		 if(this.selectedObject.isHaveBeenUpdated())
		 {
			 this.tempData = this.selectedObject.getData();
			 textRenderer.begin3DRendering();
			 for(String s:this.tempData.getValues().keySet())
			 {
				 String stringToDisplay = s+": "+this.tempData.getValues().get(s);
				 textRenderer.setColor(this.getBackgroundColor(this.color));
				 textRenderer.draw(stringToDisplay, x + 1, y - 1);
				 textRenderer.setColor(this.color);
				 textRenderer.draw(stringToDisplay, x, y);
				 y+=12;

			 }
			 textRenderer.end3DRendering();
			 this.selectedObject.setHaveBeenUpdated(false);
		 }
		 else
		 {
			 if(this.tempData != null)
			 {
				 textRenderer.begin3DRendering();
				 for(String s:this.tempData.getValues().keySet())
				 {
					 String stringToDisplay = s+": "+this.tempData.getValues().get(s);
					 textRenderer.setColor(this.getBackgroundColor(this.color));
					 textRenderer.draw(stringToDisplay, x + 1, y - 1);
					 textRenderer.setColor(this.color);
					 textRenderer.draw(stringToDisplay, x, y);
					 y+=12;

				 }
				 textRenderer.end3DRendering();
			 }
		 }
	 }

	 private final float[] compArray = new float[4];    
	 // Compute background color for best contrast
	 private Color getBackgroundColor(Color color)
	 {
		 Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), compArray);
		 if (compArray[2] > 0.5)
			 return new Color(0, 0, 0, 0.7f);
		 else
			 return new Color(1, 1, 1, 0.7f);
	 }

	 private double computeScale(java.awt.Rectangle viewport)
	 {
		 if (this.resizeBehavior.equals(AVKey.RESIZE_SHRINK_ONLY))
		 {
			 return Math.min(1d, (this.toViewportScale) * viewport.width / this.size.width);
		 }
		 else if (this.resizeBehavior.equals(AVKey.RESIZE_STRETCH))
		 {
			 return (this.toViewportScale) * viewport.width / this.size.width;
		 }
		 else if (this.resizeBehavior.equals(AVKey.RESIZE_KEEP_FIXED_SIZE))
		 {
			 return 1d;
		 }
		 else
		 {
			 return 1d;
		 }
	 }

	 private Vec4 computeLocation(java.awt.Rectangle viewport, double scale)
	 {
		 double scaledWidth = scale * this.size.width;
		 double scaledHeight = scale * this.size.height;

		 double x;
		 double y;

		 if (this.locationCenter != null)
		 {
			 x = this.locationCenter.x - scaledWidth / 2;
			 y = this.locationCenter.y - scaledHeight / 2;
		 }
		 else if (this.position.equals(AVKey.NORTHEAST))
		 {
			 x = viewport.getWidth() - scaledWidth - this.borderWidth;
			 y = viewport.getHeight() - scaledHeight - this.borderWidth;
		 }
		 else if (this.position.equals(AVKey.SOUTHEAST))
		 {
			 x = viewport.getWidth() - scaledWidth - this.borderWidth;
			 y = 0d + this.borderWidth;
		 }
		 else if (this.position.equals(AVKey.NORTHWEST))
		 {
			 x = 0d + this.borderWidth;
			 y = viewport.getHeight() - scaledHeight - this.borderWidth;
		 }
		 else if (this.position.equals(AVKey.SOUTHWEST))
		 {
			 x = 0d + this.borderWidth;
			 y = 0d + this.borderWidth;
		 }
		 else // use North East
		 {
			 x = viewport.getWidth() - scaledWidth / 2 - this.borderWidth;
			 y = viewport.getHeight() - scaledHeight / 2 - this.borderWidth;
		 }

		 if (this.locationOffset != null)
		 {
			 x += this.locationOffset.x;
			 y += this.locationOffset.y;
		 }

		 return new Vec4(x, y, 0);
	 }

	 @Override
	 public String toString()
	 {
		 return Logging.getMessage("layers.Earth.ScalebarLayer.Name");
	 }

}	
